
import random
import os
import sys
import subprocess
import json
import binascii
import time

import pyperclip
import pyotp

# from fzflib import FZF
from FZF import FZF

import Helper

from ChoiceCipher import ChoiceCipher
encryptor = ChoiceCipher()

from Cipher import GPG

from AutoComplite import AutoComplite
auto_complite = AutoComplite()

from RandomString import RandomString


class ArcAlvus:
    def __init__(self, path=None):
        if not os.path.isdir( path ):
            os.system( f"mkdir -p {path}" )
        
        self.path_locations = path
        
        self.storage = {}
        self.old_storage = {}
        
        self.storage_list = os.listdir( self.path_locations )

        self.path_cache = f"{ os.environ.get('HOME') }/.cache/arcalvus.cache"
        self.cache = {"LAST_STORAGE": None}
        
        self.show_editor_value = False

    
    def _ReadKeysFromStorageForAutoComplite(self):
        for key in self.storage:
            auto_complite.ADD_VALUE( str(key) )


    def _CacheSave(self):
        if not self.cache: return

        self.cache = json.loads(str(self.cache).replace("'", '"'))
        cache_json_dump = json.dumps(self.cache)
        
        encryptor.ENCRYPT_FILE(self.path_cache, cache_json_dump.encode())

    def _CacheRead(self):
        if not self.cache: return
        
        if not os.path.isfile( self.path_cache ): return

        self.cache = encryptor.DECRYPT_FILE(self.path_cache)
        
        self.cache = json.loads(self.cache)
        
        return self.cache
        
    def _GetLastTimeAccess(self):
        try:
            self.last_time_storage_access = os.path.getatime( f"{self.path_locations}/{self.storage_name}" )
            self.last_time_storage_modifi = os.path.getmtime( f"{self.path_locations}/{self.storage_name}" )
            self.last_time_storage_access = time.ctime( self.last_time_storage_access )
            self.last_time_storage_modifi = time.ctime( self.last_time_storage_modifi )
        except:
            self.last_time_storage_access = ""
            self.last_time_storage_modifi = ""

    def _SaveStorage(self):
        self.storage = json.loads(str(self.storage).replace("'", '"'))
        
        storage_json_dump = json.dumps(self.storage, indent=4, ensure_ascii=False)
        
        encryptor.ENCRYPT_FILE(f"{self.path_locations}/{self.storage_name}", storage_json_dump.encode())
    
    def _ReadStorage(self):
        if not os.path.isfile( f"{self.path_locations}/{self.storage_name}" ):
            print( f"{self.storage_name}: storage not exist. " )
            print( f"create a storage, use: \n$ arcalvus -C -S '{self.storage_name}' " )
            
            sys.exit()
        
        self._GetLastTimeAccess()
        
        storage = encryptor.DECRYPT_FILE(f'{self.path_locations}/{self.storage_name}')
        self.storage = json.loads(storage)
        
        self.old_storage = json.loads(storage) # for _CheckChangesStorage()
        
    def _SortedStorageList(self):
        temp = []
        for item in self.storage_list:
            time_access = os.path.getmtime(f'{self.path_locations}/{item}')
            
            temp.append( f"{time_access} {item}" )
            
        temp = sorted(temp)
        
        for k,v in enumerate(temp):
            temp[k] = ' '.join( v.split(" ")[1:] )
            
        self.storage_list = temp
    
    def _ChoiceStorage(self, storage=None):
        if storage:
            self.storage_name = storage
            return storage
        
        self.storage_list = os.listdir(self.path_locations)

        self._CacheRead()
        self._SortedStorageList()
        
        if not self.storage_list:
            print("Storage is empty. use: \n'$ arcalvus -C' \n or \n'$ arcalvus --create-storage'")
            sys.exit()
        
        if self.cache["LAST_STORAGE"]:
            self.storage_list.append( "" )
            self.storage_list.append( self.cache["LAST_STORAGE"] )
            
        self.storage_list = self.storage_list[::-1]
            
        fzf = FZF()
        # fzf.input = self.storage_list
        self.storage_name = fzf.CHOISE( self.storage_list )
        
        if not self.storage_name:
            sys.exit()
        
        self.cache["LAST_STORAGE"] = self.storage_name
        
        self._CacheSave()
        
    def _CheckChangesStorage(self):
        if self.storage == self.old_storage:
            return False
        else:
            return True
            
    def _Exit(self):
        if self._CheckChangesStorage():
            input_ = input("    save changes?, y/n: ").strip().lower()
            
            if input_ in "yes":
                self._SaveStorage()
            else: ...
            
        sys.exit()
    
    def _StorageEdit(self, read_only=False):
        print(f"\nContainer: {self.path_locations}")
        print(f"Storage: '{self.storage_name}'")
        
        print(f"\n  Last Modify: {self.last_time_storage_modifi}\n  Current:     {time.ctime( time.time() )}\n")
        
        title = {
            "storage_name": self.storage_name,
            "show_value": ""
            }
        
        while True:
            self._ReadKeysFromStorageForAutoComplite()

            if self.show_editor_value:
                title["show_value"] = '\033[31m[S]\033[0m'
            else:
                title["show_value"] = '\033[32m[H]\033[0m'
            
            input_ = input(f"| {title['show_value']} '{title['storage_name']}': ").strip().split()
            
            try:
                match input_:
                    case ["set", *arg]:
                        if arg[1].lower() == "random":
                            
                            if len(arg) > 2:
                                self.storage[ arg[0] ] = self.RANDOM(' '.join(arg[2:]))
                            else:
                                self.storage[ arg[0] ] = self.RANDOM()
                            
                            continue
                            
                        else:
                            self.storage[ arg[0] ] = ' '.join(arg[1:])
                        
                    case ["copy", *arg] | ["cp", *arg]:
                        pyperclip.copy( self.storage[ arg[0] ] )
                        
                    case ["list"] | ["ls"]:
                        for key in self.storage:
                            if self.show_editor_value:
                                print(f"    {key}: {self.storage[ key ]}")
                            else:
                                print(f"    {key}: { '*' * len(self.storage[ key ]) }")
                        print(" ")
                    
                    case ["del", *arg]:
                        for json_key in arg:
                            del self.storage[ json_key ]
                            print(f"    key removed: {json_key}")
                        print("")

                    case ["rename", *arg]:
                        old_value = self.storage[ arg[0] ]
                        self.storage[ arg[1] ] = old_value
                        
                        del self.storage[ arg[0] ]
                    
                    case ["--edit"]:
                        read_only = False
                    
                    case ["--read-only"]:
                        read_only = True
                        
                    case ["show"]:
                        self.show_editor_value = True
                        
                    case ["hide"]:
                        self.show_editor_value = False
                    
                    case ["save"]:
                        if read_only:
                            print("    Read-only. Use --edit\n")
                            continue
                            
                        self._SaveStorage()
                        
                    case ["wq"]:
                        self._SaveStorage()
                        break
                    
                    case ["help"] | ["h"]:
                        Helper.PrintHelpStorageEdit()
                    
                    case ["exit"] | ["q"]:
                        self._Exit()
                        
            except KeyError as error:
                print( f"    Invalid Key: {error}\n" )
            
            except IndexError as error:
                print( f"    Not set value in key input\n" )
                
        
    def CREATE(self, storage_name=None):
        self.storage_name = storage_name
        
        for sl in self.storage_list:
            if storage_name == sl:
                print(f"Storage: {self.storage_name} already exists.")
                return
        
        self._GetLastTimeAccess()
        self._StorageEdit()
        
    def GET_OTP(self, otp_secret_key=None):
        try:
            
            totp = pyotp.TOTP( otp_secret_key )
            key = totp.now()
            
            pyperclip.copy( key )
            
        except binascii.Error as e:
            print("Invalid OTP Key.")
            sys.exit()
        
        return key
    
    def GET(self, key=None, storage=None, show=False):
        self._ChoiceStorage(storage=storage)
        
        self._ReadStorage()
        
        if not key:
            key_list = []
            for key in self.storage:
                key_list.append( key )
            
            fzf = FZF()
            # fzf.input = key_list
            key = fzf.CHOISE( key_list )
            
        else:
            key = key
            
        response = self.storage[ key ]
        
        pyperclip.copy( response )

        if show:
            return response
        else:
            return "*" * len(response)
        
    def STORAGE_EDIT(self, storage=None, read_only=False):
        self._ChoiceStorage(storage=storage)
        self._ReadStorage()
        self._StorageEdit(read_only=read_only)
        
    def CHANGE_GPG_KEY(self, gpg_id: str):
        """decrypt the container using the old key
            and encrypt with the new one"""
        
        gpg = GPG(gpg_id)
        
        for storage_name in os.listdir(self.path_locations):
            
            storage = gpg.DECRYPT_FILE(f"{self.path_locations}/{storage_name}")
            
            gpg.ENCRYPT_FILE(f"{self.path_locations}/{storage_name}", storage)
            
            print(f"    changed gpg_key in storage: {storage_name}. [ OK ]")
            
        
    def RANDOM(self, options="20 a-zA-Z0-9 _-#@"):
        return RandomString( options )


    
    
    

